package com.iteaj.iot.taos;

import cn.hutool.core.collection.CollectionUtil;
import com.iteaj.iot.tools.db.ParamValue;
import com.iteaj.iot.tools.db.sql.SqlStatementUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SqlContext {

    private TaosSqlMeta meta;

    private static final String Insert = "insert into ";
    private static final Pattern regex = Pattern.compile("\\$\\{([^}]*)\\}");
    private static Logger logger = LoggerFactory.getLogger(SqlContext.class);

    public SqlContext(TaosSqlMeta meta) {
        this.meta = meta;
    }

    /**
     * 批量任务
     * @return
     */
    public SqlExecContext getExecContext(Collection<EntitySql> execEntitySqls) {
        if (CollectionUtil.isEmpty(execEntitySqls)) {
            return null;
        }

        StringBuilder sb = new StringBuilder(Insert);
        TaosSqlMeta meta = this.getMeta();

        Map<String, List<EntitySql>> stringListMap = new HashMap<>();
        List<ParamValue> parameterValues = new ArrayList<>();

        execEntitySqls.forEach(item -> {
            List<EntitySql> entitySqls = stringListMap.get(item.getTableName());
            if (entitySqls == null) {
                stringListMap.put(item.getTableName(), entitySqls = new ArrayList<>());
            }

            entitySqls.add(item);
        });

        stringListMap.forEach((tableName, entitySqls) -> {
            sb.append(tableName);
            if (meta.getTagInsertSql() != null) {
                // 通过第一条获取tag值
                EntitySql entitySql = entitySqls.get(0);
                sb.append(" ").append(meta.getTagInsertSql()).append(" tags ").append(meta.getTagParamSql());
                List<ParamValue> tagParams = meta.getTagParams(entitySql.getEntity());
                parameterValues.addAll(tagParams);
            }

            sb.append(" ").append(meta.getInsertSql()).append(" values ");
            entitySqls.forEach(item -> {
                sb.append(meta.getParamSql()).append(" ");
                parameterValues.addAll(item.getValues());
            });
        });

        return new SqlExecContext(sb, parameterValues.stream().toArray(ParamValue[]::new));
    }

    /**
     * 单条执行
     * @param entity
     * @return
     */
    public SqlExecContext getExecContext(Object entity) {
        final String tableName = this.getTableName(entity);
        StringBuilder sb = new StringBuilder(Insert).append(tableName).append(" ");

        List<ParamValue> parameterValues = new ArrayList<>();
        if(this.getMeta().isUsing()) { // 数据表不存在自动创建
            if(CollectionUtil.isNotEmpty(this.getMeta().getTags())) { // 使用tags创建
                sb.append(meta.getTagInsertSql()).append(" tags ").append(meta.getTagParamSql());
                parameterValues.addAll(meta.getTagParams(entity));
            }
        }

        sb.append(" ").append(meta.getInsertSql()).append(" values ").append(meta.getParamSql());
        parameterValues.addAll(this.meta.getParams(entity));

        // 默认执行1条
        return new SqlExecContext(sb, parameterValues.stream().toArray(ParamValue[]::new));
    }

    public String getTableName(Object entity) {
        // 获取数据表名
        String tableName = getMeta().getDataTableName();

        try {
            // 解析数据表表名
            Matcher matcher = regex.matcher(tableName);
            while (matcher.find()) {
                String fieldName = matcher.group(1);
                String fieldValue = null;
                if(entity instanceof Map) {
                    Object o = ((Map<?, ?>) entity).get(fieldName);
                    if(o != null) {
                        fieldValue = o.toString();
                    }
                } else {
                    fieldValue = meta.getFieldValue(fieldName, entity);
                }

                tableName = tableName.replace("${"+fieldName+"}", fieldValue);
            }

            return tableName;
        } catch (Exception e) {
            throw new TaosException("解析数据表名失败["+tableName+"], 解析上下文对象["+entity+"]", e);
        }
    }

    public int update(Object entity, DataSource dataSource) {
        return this.doUpdate(this.getExecContext(entity), dataSource, 1);
    }

    public int update(List<Object> entities, DataSource dataSource) {
        Collection<EntitySql> entitySqls = new ArrayList<>(entities.size());
        entities.forEach(entity -> {
            String value = this.getTableName(entity);

            // 解析普通字段参数
            List<ParamValue> params = this.getMeta().getParams(entity);

            entitySqls.add(new EntitySql(value, entity, params));
        });

        return this.doUpdate(this.getExecContext(entitySqls), dataSource, entities.size());
    }

    protected int doUpdate(SqlExecContext context, DataSource dataSource, int size) throws TaosException {
        int i;
        long start = System.currentTimeMillis();
        try {
            Object[] objects = Arrays.stream(context.getValues()).map(item -> item.getFieldValue()).toArray();
            i = SqlStatementUtil.execute(dataSource.getConnection(), context.getSql().toString(), objects);
            writeLogger(context, null, start, size);
        } catch (SQLException e) {
            this.writeLogger(context, e, start, size);
            throw new TaosException(e);
        }

        return i;
    }

    private void writeLogger(SqlExecContext execContext, Throwable cause, long start, int size) {
        if (logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder();
            for (ParamValue value : execContext.getValues()) {
                sb.append(value.getFieldValue()).append(", ");
            }

            logger.trace("TAOS适配 数据入库({}ms) - 条数：{}\r\n\t    Sql：{} \r\n\t params：{}"
                    , System.currentTimeMillis() - start, size, execContext.getSql().toString()
                    , sb.substring(0, sb.length() - 2), cause);
        }
    }

    public TaosSqlMeta getMeta() {
        return meta;
    }

    public void setMeta(TaosSqlMeta meta) {
        this.meta = meta;
    }

}
